package gek.uni.diplom.ocl.example.visitors;

import java.util.List;

import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EOperation;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.ocl.ecore.internal.OCLFactoryImpl;
import org.eclipse.ocl.ecore.internal.OCLStandardLibraryImpl;
import org.eclipse.ocl.expressions.BooleanLiteralExp;
import org.eclipse.ocl.expressions.IntegerLiteralExp;
import org.eclipse.ocl.expressions.IterateExp;
import org.eclipse.ocl.expressions.IteratorExp;
import org.eclipse.ocl.expressions.OCLExpression;
import org.eclipse.ocl.expressions.OperationCallExp;
import org.eclipse.ocl.expressions.PropertyCallExp;
import org.eclipse.ocl.expressions.RealLiteralExp;
import org.eclipse.ocl.expressions.StringLiteralExp;
import org.eclipse.ocl.expressions.TypeExp;
import org.eclipse.ocl.expressions.Variable;
import org.eclipse.ocl.expressions.VariableExp;
import org.eclipse.ocl.types.PrimitiveType;
import org.eclipse.ocl.util.OCLStandardLibraryUtil;
import org.eclipse.ocl.utilities.AbstractVisitor;
import org.eclipse.ocl.utilities.PredefinedType;

/**
 * Посетитель OCL выражения порядка трансформирует в эквивалентные отношения
 * порядка, в которых слева находится выражение от переменных, а справа - от
 * констант
 * 
 * @author Gek
 * 
 * @param <C>
 * @param <O>
 * @param <P>
 * @param <EL>
 * @param <PM>
 * @param <S>
 * @param <COA>
 * @param <SSA>
 * @param <CT>
 */

// TODO Arkady Результатом разбора выражений obj1 = obj2 будет obj1 - obj2 = 0,
// а надо obj1 = obj2

public class SimplifierVisitor<C, O, P, EL, PM, S, COA, SSA, CT> extends
    AbstractVisitor<OCLExpression<C>, C, O, P, EL, PM, S, COA, SSA, CT> {

  @Override
  protected OCLExpression<C> handlePropertyCallExp(
      PropertyCallExp<C, P> callExp, OCLExpression<C> sourceResult,
      List<OCLExpression<C>> qualifierResults) {
    return callExp;
  }

  @SuppressWarnings("unchecked")
  @Override
  protected OCLExpression<C> handleOperationCallExp(
      OperationCallExp<C, O> callExp, OCLExpression<C> sourceResult,
      List<OCLExpression<C>> argumentResults) {

    int opCode = callExp.getOperationCode();

    switch (opCode) {
    case PredefinedType.EQUAL:
    case PredefinedType.NOT_EQUAL:
    case PredefinedType.LESS_THAN:
    case PredefinedType.LESS_THAN_EQUAL:
    case PredefinedType.GREATER_THAN:
    case PredefinedType.GREATER_THAN_EQUAL:
      SplitterVisitor<C, O, P, EL, PM, S, COA, SSA, CT> splitter = new SplitterVisitor<C, O, P, EL, PM, S, COA, SSA, CT>();
      OCLExpression<C>[] leftSplit = sourceResult.accept(splitter);
      OCLExpression<C>[] rightSplit = argumentResults.get(0).accept(splitter);
      OperationCallExp<C, O> result = (OperationCallExp<C, O>) EcoreUtil
          .copy(callExp);

      // Рассматриваем вариант a ? b, где a и b - экземпляры не примитивного
      // типа. При таком раскладе leftSplit[1] и rightSplit[1] должны быть равны
      // null, и типы leftSplit[0] и rightSplit[0] должны совпадать
      if (leftSplit[0] != null && rightSplit[0] != null && leftSplit[1] == null
          && rightSplit[1] == null && !isArithmeticType(leftSplit[0].getType())
          && !isArithmeticType(rightSplit[0].getType())
          && leftSplit[0].getType().equals(rightSplit[0].getType()))
        return callExp;
      else
      // Рассматриваем вариант a ? b, где a и b - экземпляры не примитивного
      // типа. При таком раскладе leftSplit[0] и rightSplit[0] должны быть равны
      // null, и типы leftSplit[1] и rightSplit[1] должны совпадать
      if (leftSplit[1] != null && rightSplit[1] != null && leftSplit[0] == null
          && rightSplit[0] == null && isArithmeticType(leftSplit[1].getType())
          && isArithmeticType(rightSplit[1].getType())
          && leftSplit[1].getType().equals(rightSplit[1].getType()))
        return callExp;

      OCLExpression<C> left = createOperationCallExpression(leftSplit[0],
          rightSplit[0], PredefinedType.MINUS);
      OCLExpression<C> right = createOperationCallExpression(rightSplit[1],
          leftSplit[1], PredefinedType.MINUS);
      // Левые и правые не могут одновременно быть null
      if (left == null) {
        left = createNullIntegerLiteralExp(right.getType());
      }
      if (right == null) {
        right = createNullIntegerLiteralExp(left.getType());
      }
      isArithmeticType(left.getType());

      result.setSource((OCLExpression<C>) EcoreUtil.copy(left));
      result.getArgument().clear();
      result.getArgument().add((OCLExpression<C>) EcoreUtil.copy(right));
      return result;
    default:
      result = (OperationCallExp<C, O>) EcoreUtil.copy(callExp);
      result.setSource((OCLExpression<C>) EcoreUtil.copy(sourceResult));
      result.getArgument().clear();
      try{
      result.getArgument().add(
          (OCLExpression<C>) EcoreUtil.copy(argumentResults.get(0)));
      } catch (Exception e) {
        System.out.println(e.getLocalizedMessage());
      }
      return result;
    }
  }

  @Override
  public OCLExpression<C> visitIntegerLiteralExp(IntegerLiteralExp<C> literalExp) {
    return literalExp;
  }

  @Override
  public OCLExpression<C> visitRealLiteralExp(RealLiteralExp<C> literalExp) {
    return literalExp;
  }

  @Override
  public OCLExpression<C> visitVariableExp(VariableExp<C, PM> v) {
    return v;
  }

  @Override
  public OCLExpression<C> visitBooleanLiteralExp(BooleanLiteralExp<C> literalExp) {
    return literalExp;
  }

  @Override
  public OCLExpression<C> visitStringLiteralExp(StringLiteralExp<C> literalExp) {
    return literalExp;
  }

  @Override
  public OCLExpression<C> visitTypeExp(TypeExp<C> t) {
    return t;
  }

  @Override
  protected OCLExpression<C> handleIterateExp(IterateExp<C, PM> callExp,
      OCLExpression<C> sourceResult, List<OCLExpression<C>> variableResults,
      OCLExpression<C> resultResult, OCLExpression<C> bodyResult) {
    return callExp;
  }

  @Override
  protected OCLExpression<C> handleIteratorExp(IteratorExp<C, PM> callExp,
      OCLExpression<C> sourceResult, List<OCLExpression<C>> variableResults,
      OCLExpression<C> bodyResult) {
    // TODO Auto-generated method stub
    return callExp;
  }

  /**
   * Создает операцию с кодом opCode и операндами left и right. XXX Arkady Не
   * очень хороший метод, т.к. привязан к ECore
   * 
   * @param left
   * @param right
   * @param opCde
   * @return
   */
  private OCLExpression<C> createOperationCallExpression(OCLExpression<C> left,
      OCLExpression<C> right, int opCode) {

    if (right == null)
      return left;

    if (left == null) {
      if (right == null)
        return null;
      else {
        switch (opCode) {
        case PredefinedType.PLUS:
          return right;
        default:
          IntegerLiteralExp<C> literal = OCLFactoryImpl.INSTANCE
              .createIntegerLiteralExp();
          literal.setType(right.getType());
          literal.setIntegerSymbol(0);
          OperationCallExp<C, O> result = OCLFactoryImpl.INSTANCE
              .createOperationCallExp();
          result.setOperationCode(opCode);
          result.setReferredOperation((O) createEmptyEcoreOperation(literal
              .getType(), opCode));
          result.setSource(literal);
          result.getArgument().clear();
          result.getArgument().add((OCLExpression<C>) EcoreUtil.copy(right));
          return result;
        }
      }
    } else { // left != null && right!= null
      OperationCallExp<C, O> result = OCLFactoryImpl.INSTANCE
          .createOperationCallExp();
      result.setType(left.getType());
      result.setOperationCode(opCode);
      result.setReferredOperation((O) createEmptyEcoreOperation(left.getType(),
          opCode));

      result.setSource((OCLExpression<C>) EcoreUtil.copy(left));
      result.getArgument().clear();
      result.getArgument().add((OCLExpression<C>) EcoreUtil.copy(right));
      return result;
    }
  }

  /**
   * Создает экземпляр типа EOperation, у которой тип type, и код opCode
   * 
   * @param type
   *          - тип операции
   * @param opCode
   *          - код операции
   * @return
   */
  @SuppressWarnings("restriction")
  public EOperation createEmptyEcoreOperation(C type, int opCode) {
    List<EOperation> operations = OCLStandardLibraryImpl
        .getExistingOperations((EClassifier) type);
    for (EOperation op : operations) {
      if (OCLStandardLibraryUtil.getOperationCode(op.getName()) == opCode)
        return (EOperation) op;
    }
    return null;
  }

  private IntegerLiteralExp<C> createNullIntegerLiteralExp(C type) {
    IntegerLiteralExp<C> result = OCLFactoryImpl.INSTANCE
        .createIntegerLiteralExp();
    result.setType(type);
    result.setIntegerSymbol(0);
    return result;
  }

  private boolean isArithmeticType(C type) {
    return type instanceof PrimitiveType;
  }
}